﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using System.Text.RegularExpressions;
//using System.Web.Script.Serialization;
using Newtonsoft.Json.Linq;

namespace Web.Helpers.jqGridHelpers
{
    /// <summary>
    /// 
    /// </summary>
    public class jqGridSearchFilterBuilder
    {
        #region > Constants

        /// <summary>
        /// 
        /// </summary>
        public class LogicalOperators
        {
            /// <summary>
            /// 
            /// </summary>
            public const string OR = "OR";
            /// <summary>
            /// 
            /// </summary>
            public const string AND = "AND";
        }

        /// <summary>
        /// 
        /// </summary>
        public class EqualityOperators
        {
            /// <summary>
            /// 
            /// </summary>
            public const string Equal = "==";
            //public const string Equal               = "Equals";
            /// <summary>
            /// 
            /// </summary>
            public const string NotEqual = "!=";
            /// <summary>
            /// 
            /// </summary>
            public const string GreaterThan = ">";
            /// <summary>
            /// 
            /// </summary>
            public const string GreaterThanOrEqual = ">=";
            /// <summary>
            /// 
            /// </summary>
            public const string LessThan = "<";
            /// <summary>
            /// 
            /// </summary>
            public const string LessThanOrEqual = "<=";
            /// <summary>
            /// 
            /// </summary>
            public const string In = "In";
            /// <summary>
            /// 
            /// </summary>
            public const string NotIn = "Not In";
            /// <summary>
            /// 
            /// </summary>
            public const string Contains = "Contains";
            /// <summary>
            /// 
            /// </summary>
            public const string NotContains = "";
            /// <summary>
            /// 
            /// </summary>
            public const string StartsWith = "StartsWith";
            /// <summary>
            /// 
            /// </summary>
            public const string NotStartsWith = "";
            /// <summary>
            /// 
            /// </summary>
            public const string EndsWith = "";
            /// <summary>
            /// 
            /// </summary>
            public const string NotEndsWith = "";
            /// <summary>
            /// 
            /// </summary>
            public const string IsNull = "string.IsNullOrEmpty( )";
            /// <summary>
            /// 
            /// </summary>
            public const string IsNotNull = "";
        }

        /// <summary>
        /// Set of Rules
        /// </summary>
        public class RuleSet
        {
            public RuleSet()
            {
                this.Rules = new List<Rule>();
            }

            private string _operator = string.Empty;
            /// <summary>
            /// 
            /// </summary>
            public string Operator
            {
                get { return _operator; }
                set { _operator = value; }
            }

            private List<Rule> _rules = null;
            /// <summary>
            /// 
            /// </summary>
            public List<Rule> Rules
            {
                get { return _rules; }
                set { _rules = value; }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public class Rule
        {
            private string _match = string.Empty;
            /// <summary>
            /// 
            /// </summary>
            public string Match
            {
                get { return _match; }
                set { _match = value; }
            }

            private string _operator = string.Empty;
            /// <summary>
            /// 
            /// </summary>
            public string Operator
            {
                get { return _operator; }
                set { _operator = value; }
            }

            private string _field = string.Empty;
            /// <summary>
            /// 
            /// </summary>
            public string Field
            {
                get { return _field; }
                set { _field = value; }
            }

            private string _data = string.Empty;
            /// <summary>
            /// 
            /// </summary>
            public string Data
            {
                get { return _data; }
                set { _data = value; }
            }
        }

        #endregion

        #region > Constructors

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="colModel"></param>
        public jqGridSearchFilterBuilder(string filter, string colModel)
        {
            this.RuleSets = new List<RuleSet>();

            if (String.IsNullOrEmpty(filter) || String.IsNullOrEmpty(colModel))
                return;

            var obj = JObject.Parse(filter);
            var list = (JArray)obj["rules"];

            #region > Create columns info dictionary
            var colsInfo = new Dictionary<string, Type>();
            var col = JObject.Parse(colModel);
            var cols = (JArray)col["Cols"];
            foreach (var item in cols)
            {
                var key   = (string)item["name"];
                var value = (string)item["type"];

                if (!colsInfo.ContainsKey(key))
                    colsInfo.Add(key, this.SwitchType(value));
            }
            this.ColumnsInfo = colsInfo;
            #endregion

            foreach (var item in list)
            {
                if ( item["set"] != null )
                {
                    var ruleSet             = new RuleSet { Operator = (string) item["condition"] };
                    var listOfRules         = (JArray)item["set"];
                    foreach(var itemRule in listOfRules)
                    {
                        var rule = new Rule
                        {
                            Match       = this.GetLogicalOperator((string) itemRule["match"]),
                            Field       = (string) itemRule["field"],
                            Operator    = this.GetEqualityOperator((string) itemRule["op"]),
                            Data        = (string) itemRule["data"]
                        };
                        ruleSet.Rules.Add( rule );
                    }
                    this.RuleSets.Add ( ruleSet );
                }
                else if ( item["match"] != null )
                {
                    var ruleSet = new RuleSet();
                    var rule = new Rule
                    {
                        Match       = this.GetLogicalOperator((string)item["match"]),
                        Field       = (string) item["field"],
                        Operator    = this.GetEqualityOperator((string)item["op"]),
                        Data        = (string)item["data"]
                    };
                    ruleSet.Rules.Add(rule);
                    this.RuleSets.Add(ruleSet);
                }
            }
        }

        #endregion

        #region > Properties

        private string _filter;
        /// <summary>
        /// 
        /// </summary>
        public string Filter
        {
            get { return _filter; }
            set { _filter = value; }
        }

        private List<RuleSet> _ruleSets;
        /// <summary>
        /// 
        /// </summary>
        public List<RuleSet> RuleSets
        {
            get { return _ruleSets; }
            set { _ruleSets = value; }
        }        

        //private List<Rule> _rules;
        ///// <summary>
        ///// 
        ///// </summary>
        //public List<Rule> Rules
        //{
        //    get { return _rules; }
        //    set { _rules = value; }
        //}

        private Dictionary<string, Type> _colInfo;
        /// <summary>
        /// 
        /// </summary>
        public Dictionary<string, Type> ColumnsInfo
        {
            get { return _colInfo; }
            set { _colInfo = value; }
        }

        #endregion

        #region > Public Methods

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public string ToQuery()
        {
            var sb = new StringBuilder(string.Empty);

            bool startsNewSet = true;
            foreach (var set in this.RuleSets)
            {
                if (!startsNewSet)
                    sb.Append(" " + this.GetLogicalOperator(set.Operator) + " ");
                else
                    startsNewSet = false;

                sb.Append("( ");
                this.AppendRuleSet(sb, set);
                sb.Append(" )");
            }

            return sb.ToString();
        }

        public List<Rule> GetMatchingRules(string field)
        {
            var rules = new List<Rule>();
            foreach (var ruleSet in this.RuleSets)
            {
                rules.AddRange(ruleSet.Rules.Where(rule => rule.Field.ToLower() == field.ToLower()));
            }
            return rules;
        }


        #endregion

        #region > Private Methods

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cond"></param>
        /// <returns></returns>
        private string GetLogicalOperator(string cond)
        {
            switch (cond.ToUpper().Replace("\"", string.Empty))
            {
                case "AND" :
                    return LogicalOperators.AND;
                case "OR":
                    return LogicalOperators.OR;
            }
            return string.Empty;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="op"></param>
        /// <returns></returns>
        private string GetEqualityOperator(string op)
        {
            switch (op.ToLower())
            {
                case "eq":
                case "=":
                    return EqualityOperators.Equal;
                case "ne":
                case "!=":
                    return EqualityOperators.NotEqual;
                case "cn":
                    return EqualityOperators.Contains;
                case "nc":
                    return EqualityOperators.NotContains;
                case "gt":
                case ">":
                    return EqualityOperators.GreaterThan;
                case "ge":
                case ">=":
                    return EqualityOperators.GreaterThanOrEqual;
                case "lt":
                case "<":
                    return EqualityOperators.LessThan;
                case "le":
                case "<=":
                    return EqualityOperators.LessThanOrEqual;
                case "in":
                    return EqualityOperators.In;
                case "ni":
                    return EqualityOperators.NotIn;
                case "nu":
                    return EqualityOperators.IsNull;
                case "nn":
                    return EqualityOperators.IsNotNull;
                case "bw":
                    return EqualityOperators.StartsWith;
                case "bn":
                    return EqualityOperators.NotStartsWith;
                case "ew":
                    return EqualityOperators.EndsWith;
                case "en":
                    return EqualityOperators.NotEndsWith;
            }
            return string.Empty;
        }

        /// <summary>
        /// Removes all non-numeric chars from a string
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private string RemoveNonNumeric(string value)
        {
            //Remove all except period ".", minus sign "-", and integers (numbers from 0 to 9)..
            return Regex.Replace(value, "[^-.,0-9]", "");
            //return Regex.Replace(value, "[^.,0-9]", "");
        }

        private void AppendRuleSet(StringBuilder sb, RuleSet set)
        {
            bool startsNewRule = true;
            foreach(Rule rule in set.Rules)
            {
                this.AppendRule(sb, rule, startsNewRule);
                if ( startsNewRule )
                    startsNewRule = false;
            }
        }

        /// <summary>
        /// Generates string with search logic
        /// </summary>
        /// <param name="sb"></param>
        /// <param name="rule"></param>
        /// <param name="rulesOp"></param>
        private void AppendRule(StringBuilder sb, Rule rule, bool startsNewRule)
        {
            string op       = rule.Operator;
            string data     = rule.Data;
            string field    = rule.Field;
            Type fieldType  = this.GetDataType(field);

            if ( !startsNewRule )
                sb.AppendFormat(" " + rule.Match + " ");

            #region > Generate search logic
            if (op == "Contains")
            {
                #region Contains
                if ( fieldType == typeof(String) )
                {
                    sb.AppendFormat("{0}.ToUpperInvariant()." + op + "(\"{1}\")", field, data.ToUpperInvariant());
                }
                else if ( fieldType == typeof(Enum) )
                {
                    sb.AppendFormat("{0} " + op + " \"{1}\"", field, data);
                }
                #endregion
            }
            else
            {
                #region All except Contains
                if ( op.Equals(EqualityOperators.In) || op.Equals(EqualityOperators.NotIn) )
                {
                    var list = data.Split(',');
                    var listAppended = string.Empty;
                    var isFirstItem = true;

                    var idsList = "";
                    foreach (var item in list)
                    {
                        //listAppended += isFirstItem ? "" : ",";
                        //listAppended += string.Format("'{0}'", item);
                        //isFirstItem = false;
                        
                        idsList += isFirstItem ? "" : ",";
                        idsList += String.Format("Convert.ToInt32(\"{0}\")", item);
                        isFirstItem = false;
                    }
                    var ids = "new int[] {" + idsList + "}";
                    op = "Contains";
                    sb.AppendFormat("{0}." + op + "({1})", ids, field);
                    //var ids = new int[] {1, 2};
                    //new List<Guion>().Where(x => ids.Contains(x.Id));
                }
                if ( op.Equals(EqualityOperators.StartsWith) )
                {
                    sb.AppendFormat("{0}." + op + "( \"{1}\" )", field, data);
                }
                else if ( fieldType == typeof(Int64) )
                {
                    data = this.RemoveNonNumeric(data);
                    sb.AppendFormat("{0} " + op + " Convert.ToInt64(\"{1}\")", field, data);
                }
                else if ( fieldType == typeof(Int32) )
                {
                    data = this.RemoveNonNumeric(data);
                    sb.AppendFormat("{0} " + op + " Convert.ToInt32(\"{1}\")", field, data);
                }
                else if (fieldType == typeof(Decimal) )
                {
                    data = this.RemoveNonNumeric(data);
                    sb.AppendFormat("{0} " + op + " Convert.ToDecimal(\"{1}\")", field, data);
                }
                else if ( fieldType == typeof(DateTime) )
                {
                    sb.AppendFormat("{0}.Date " + op + " Convert.ToDateTime(\"{1}\")", field, data);
                }
                else if ( fieldType == typeof(DateTime?) )
                {
                    sb.AppendFormat("{0}.Value.Date " + op + " Convert.ToDateTime(\"{1}\")", field, data);
                }
                else if ( fieldType == typeof(Boolean) )
                {
                    if (data == "1" || data.ToLower() == "true")
                        sb.AppendFormat("{0}", field);
                    else
                        sb.AppendFormat("!{0}", field);
                }
                else if ( fieldType == typeof(Enum) )
                {
                    sb.AppendFormat("{0} " + op + " \"{1}\"", field, data);
                }
                else if ( fieldType == typeof(String) )
                {
                    sb.AppendFormat("{0}.ToUpperInvariant() " + op + " \"{1}\"", field, data.ToUpperInvariant());
                }
                #endregion
            }
            #endregion
        }

        private Type GetDataType(string key)
        {
            var objType = typeof(String);
            try
            {
                objType = this.ColumnsInfo[key];
            }
            catch (Exception ex)
            {
                var exception = ex.Message;
                objType = typeof(String);
            }
            return objType;
        }

        private Type SwitchType(string type)
        {
            var objType = typeof(String);
            switch (type.ToLower())
            {
                case "int64":
                    objType = typeof(Int64);
                    break;
                case "int32":
                    objType = typeof(Int32);
                    break;
                case "decimal":
                    objType = typeof(Decimal);
                    break;
                case "datetime":
                    objType = typeof(DateTime);
                    break;
                case "datetime?":
                    objType = typeof(DateTime?);
                    break;
                case "boolean":
                    objType = typeof(Boolean);
                    break;
                case "guid":
                    objType = typeof(Guid);
                    break;
                case "enum":
                    objType = typeof(Enum);
                    break;
                case "integer":
                    objType = typeof(Int32);
                    break;
                case "string":
                    objType = typeof(String);
                    break;
            }
            return objType;
        }

        #endregion
    }
}
